home *** CD-ROM | disk | FTP | other *** search
/ Technotools / Technotools (Chestnut CD-ROM)(1993).ISO / lang_pas / mtask11 / mtask.pas < prev    next >
Pascal/Delphi Source File  |  1988-11-12  |  13KB  |  527 lines

  1. UNIT mtask;
  2.  
  3.  
  4. {MTASK 1.1, a simple multi-tasker unit for Turbo Pascal 5.
  5.  
  6. Written in November, 1988, and donated to the public domain by:
  7.  
  8.    Wayne E. Conrad
  9.    2627 North 51st Ave, #219
  10.    Phoenix, AZ  85035
  11.    BBS: (602) 484-9356, 300/1200/2400, 24 hours/day
  12.  
  13. This unit provides Turbo Pascal 5 with what I call "request driven"
  14. multi-tasking.  Switching from the current task to another task is done
  15. whenever the current task requests a task switch by calling procedure
  16. "switch_task."  No interrupt driven context switching is done, because
  17. it's a hassle.
  18.  
  19. See accompanying files for documentation and examples.}
  20.  
  21.  
  22. {$F+}  {Most procedures in this unit must be FAR}
  23.  
  24.  
  25. INTERFACE
  26.  
  27.  
  28. {The maximum number of tasks.  Modify to suit your needs.}
  29.  
  30. CONST
  31.   max_tasks = 10;
  32.  
  33.  
  34. {Result codes.  0 is "no error"}
  35.  
  36. CONST
  37.   heap_full       = 1;   {Unable to allocate heap for the task's stack}
  38.   too_many_tasks  = 2;   {Maximum number of tasks are already running}
  39.   invalid_task_id = 3;   {There is no task with that ID number}
  40.  
  41.  
  42. {This is the procedure type for a task.  The parent task can pass any
  43. type of variable to pass information to the child task.}
  44.  
  45. TYPE
  46.   task_proc = PROCEDURE (VAR param);
  47.  
  48.  
  49. {A task number is the number used internally by this unit to identify
  50. a task.  It is a direct index into the task_info array.}
  51.  
  52. TYPE
  53.   task_number = 1..max_tasks;
  54.  
  55.  
  56. {A task id is the number used by other units to identify a task.  A
  57. task id is translated into task numbers through the array id_index
  58. (below). }
  59.  
  60. TYPE
  61.   task_id = 1..max_tasks;
  62.  
  63.  
  64. {This record contains all the information about a task, as follows:
  65.  
  66.   stack_ptr:   Saved stack segment (ss) and stack pointer (sp) registers
  67.  
  68.   stack_org:   If the stack is stored on the heap, this is the address of
  69.                the beginning of the block of memory allocated for the stack.
  70.  
  71.   stack_bytes: Size of stack on the heap, or 0 if the stack is not on the
  72.                heap.  If the stack is not on the heap, then this field is 0.
  73.  
  74.   bp:          Saved value of base pointer (BP) register.
  75.  
  76.   id:          The id number of the task
  77.  
  78. Note that DS (Data Segment register) is not stored.  We can get away with
  79. this by assuming that all tasks will use the same data segment.}
  80.  
  81. TYPE
  82.   task_rec =
  83.     RECORD
  84.     stack_ptr  : Pointer;
  85.     stack_org  : Pointer;
  86.     stack_bytes: Word;
  87.     bp         : Word;
  88.     id         : task_id;
  89.     END;
  90.  
  91.  
  92. {This array type is used to store information for each task.}
  93.  
  94. TYPE
  95.   task_info_array = ARRAY [task_number] OF task_rec;
  96.  
  97.  
  98. {See the IMPLEMENTATION section for descriptions of these procedures and
  99. functions.}
  100.  
  101. PROCEDURE create_task
  102.   (
  103.   task      : task_proc;
  104.   VAR param ;
  105.   stack_size: Word;
  106.   VAR id    : Word;
  107.   VAR result: Word
  108.   );
  109. PROCEDURE terminate_task (id: Word; VAR result: Word);
  110. PROCEDURE switch_task;
  111. FUNCTION current_task_id: task_id;
  112. FUNCTION number_of_tasks: task_number;
  113. PROCEDURE get_task_info
  114.   (
  115.   VAR info: task_info_array;
  116.   VAR n   : task_number
  117.   );
  118.  
  119.  
  120. IMPLEMENTATION
  121.  
  122.  
  123. {For each task id, this array gives the task number.  When a calling unit
  124. gives a task id, this array is used to convert it into a task number.  If
  125. id_index [id] = 0, then id is unused.  If id_index [id] is not zero, then
  126. it's the task number of the task with that id.}
  127.  
  128. VAR
  129.   id_index: ARRAY [task_id] OF 0..max_tasks;
  130.  
  131.  
  132. {The number of tasks in the system}
  133.  
  134. VAR
  135.   ntasks: task_number;
  136.  
  137.  
  138. {Information for each task.}
  139.  
  140. VAR
  141.   task_info: task_info_array;
  142.  
  143.  
  144. {This is the task number of the currently executing task}
  145.  
  146. VAR
  147.   current_task: task_number;
  148.  
  149.  
  150. {This is the record type of the initial contents of the stack when a task
  151. is created.  When the task is first switched to, it will be from within
  152. the switch_task, terminate_task, or terminate_current_task procedure.
  153. At the end of switch_task, BP will be popped, then a far return
  154. will be done.  The far return will transfer to the beginning
  155. of task.  The task can access the parameter "task_param," which is a pointer to
  156. whatever data structure that the creator of this task wanted to pass to the
  157. new task.  When the task finally exits, a far return to "end_task"
  158. will be done.  The exception is the main task, which ends the program
  159. completely if it exits.}
  160.  
  161. TYPE
  162.   initial_stack_rec_ptr = ^initial_stack_rec;
  163.   initial_stack_rec =
  164.     RECORD
  165.     bp        : Word;
  166.     task_addr : task_proc;
  167.     end_task  : Pointer;
  168.     task_param: Pointer;
  169.     END;
  170.  
  171.  
  172. {Remove a task's information from the task info array, and decrement
  173. the number of tasks.}
  174.  
  175. PROCEDURE delete_task_info (task_num: task_number);
  176. VAR
  177.   i: task_number;
  178. BEGIN
  179.   FOR i := task_num TO ntasks - 1 DO
  180.     BEGIN
  181.     task_info [i] := task_info [i + 1];
  182.     END;
  183.   Dec (ntasks);
  184. END;
  185.  
  186.  
  187. {Terminate the current task.  If the current task is the only task, then
  188. the program is halted.  If the current task's stack was allocated from the
  189. heap, it is freed.}
  190.  
  191. PROCEDURE terminate_current_task;
  192.  
  193.  
  194. {These are defined as constants to force them into the data segment.  They
  195. can't be local, because local variables are stored on the stack and we're
  196. going to switch to a different task before we're done with these variables.}
  197.  
  198. CONST
  199.   old_stack_org  : Pointer = NIL;
  200.   old_stack_bytes: Word = 0;
  201.  
  202.  
  203. VAR
  204.   task_num : task_number;
  205.   new_stack: Pointer;
  206.   new_bp   : Word;
  207.  
  208.  
  209. BEGIN
  210.  
  211.   {If we're the last task left, then exit to DOS}
  212.  
  213.   IF ntasks <= 1 THEN
  214.     Halt;
  215.  
  216.   {Free up the task id so that it can be reused when another task is
  217.   created.  Remember where the task's stack is so that we can free it up
  218.   if it's on the heap.  We can't free it now, because we're still using it!}
  219.  
  220.   WITH task_info [current_task] DO
  221.     BEGIN
  222.     id_index [id]   := 0;
  223.     old_stack_org   := stack_org;
  224.     old_stack_bytes := stack_bytes;
  225.     END;
  226.  
  227.   {Remove the task's information from the task info array}
  228.  
  229.   delete_task_info (current_task);
  230.   IF current_task > ntasks THEN
  231.     current_task := 1;
  232.  
  233.   {Switch to the next task.  The stack_ptr and bp are transfered into local
  234.   variables because it's much easier to access simple variables in
  235.   INLINE code than it is to access array variables.}
  236.  
  237.   WITH task_info [current_task] DO
  238.     BEGIN
  239.     new_stack := stack_ptr;
  240.     new_bp    := bp;
  241.     END;
  242.   INLINE
  243.     (
  244.     $8b/$86/>new_stack+0/     {MOV  AX,[BP].NEW_STACK+0}
  245.     $8b/$96/>new_stack+2/     {MOV  DX,[BP].NEW_STACK+2}
  246.     $8b/$ae/>new_bp/          {MOV  BP,[BP].NEW_BP}
  247.     $fa/                      {CLI}
  248.     $8e/$d2/                  {MOV  SS,DX}
  249.     $8b/$e0/                  {MOV  SP,AX}
  250.     $fb                       {STI}
  251.     );
  252.  
  253.   {If the task we just got rid of had its heap on the stack, then release
  254.   that memory back to the free pool.}
  255.  
  256.   IF old_stack_bytes > 0 THEN
  257.     FreeMem (old_stack_org, old_stack_bytes);
  258. END;
  259.  
  260.  
  261. {Terminate a task.  If task_id is 0, then the current task is deleted.
  262. Possible result codes are:
  263.  
  264.   0                   No error
  265.   invalid_task_id     There is no task with that ID number}
  266.  
  267. PROCEDURE terminate_task (id: Word; VAR result: Word);
  268.  
  269.  
  270.   {Delete a task.  Do not use to delete the current task!}
  271.  
  272.   PROCEDURE delete_task (task_num: task_number);
  273.   VAr
  274.     i: task_number;
  275.   BEGIN
  276.     id_index [id] := 0;
  277.     WITH task_info [task_num] DO
  278.       IF stack_bytes > 0 THEN
  279.         FreeMem (stack_org, stack_bytes);
  280.     delete_task_info (task_num);
  281.     IF current_task > task_num THEN
  282.       Dec (current_task);
  283.   END;
  284.  
  285.  
  286. VAR
  287.   task_num: task_number;
  288.  
  289. BEGIN {terminate_task}
  290.   result := 0;
  291.   IF id = 0 THEN
  292.     terminate_current_task
  293.   ELSE
  294.     IF (id < 1) OR (id > max_tasks) THEN
  295.       result := invalid_task_id
  296.     ELSE
  297.       BEGIN
  298.       task_num := id_index [id];
  299.       IF task_num = current_task THEN
  300.         terminate_current_task
  301.       ELSE
  302.         IF task_num = 0 THEN
  303.           result := invalid_task_id
  304.         ELSE
  305.           delete_task (task_num);
  306.       END;
  307. END;
  308.  
  309.  
  310. {Create a new task and pass parameter "param" to it.  Stack space for
  311. the task is allocated from the heap, and the stack is initialized
  312. so that procedure "new_task" will be executed with parameter "param".
  313. Result codes are:
  314.  
  315.   0                  No error occured
  316.   heap_full          Unable to allocate heap for the task's stack
  317.   too_many_tasks     Maximum number of tasks are already running
  318.  
  319. If an error occurs, then id is not set.  Otherwise, id is the task
  320. id of the newly created task.}
  321.  
  322. PROCEDURE create_task
  323.   (
  324.   task      : task_proc;
  325.   VAR param ;
  326.   stack_size: Word;
  327.   VAR id    : Word;
  328.   VAR result: Word
  329.   );
  330.  
  331.  
  332. {This is the task number of the task we're creating}
  333.  
  334. VAR
  335.   task_num: task_number;
  336.  
  337.  
  338.   {Allocate stack space for the task.  The minimum allowable
  339.   requested stack size is 512 bytes.  For some reason, the stack-check
  340.   procedure in Turbo's run-time library has that limit hard-coded into
  341.   it.
  342.  
  343.   stack_org is set to the address of the beginning of the block of memory
  344.   allocated for the stack.
  345.  
  346.   stack_bytes is set to the size of the block of memory allocated for the
  347.   stack.}
  348.  
  349.   PROCEDURE create_stack;
  350.   BEGIN
  351.     IF stack_size < 512 THEN
  352.       stack_size := 512;
  353.     IF stack_size > MaxAvail THEN
  354.       result := heap_full
  355.     ELSE
  356.       WITH task_info [task_num] DO
  357.         BEGIN
  358.         GetMem (stack_org, stack_size);
  359.         stack_bytes := stack_size;
  360.         END;
  361.   END;
  362.  
  363.  
  364.   {Initialize the stack and the stack pointer.  The structure
  365.   "initial_stack_rec" is placed at the top of the stack area, with the
  366.   stack pointer pointing to its lowest element.  See the comments
  367.   for initial_stack_rec for what the stuff in initial_stack_rec
  368.   actually does.}
  369.  
  370.   PROCEDURE init_stack;
  371.   VAR
  372.     stack_ofs: Word;
  373.   BEGIN
  374.     WITH task_info [task_num] DO
  375.       BEGIN
  376.       stack_ofs := Ofs (stack_org^) + stack_bytes - Sizeof (initial_stack_rec);
  377.       stack_ptr := Ptr (Seg (stack_org^), stack_ofs);
  378.       bp := Ofs (stack_ptr^);
  379.       WITH initial_stack_rec_ptr (stack_ptr)^ DO
  380.         BEGIN
  381.         task_param := @param;
  382.         task_addr  := task;
  383.         end_task   := @terminate_current_task;
  384.         bp         := 0;
  385.         END;
  386.       END;
  387.   END;
  388.  
  389.  
  390.   {Find an unused task id and assign it to the new task}
  391.  
  392.   PROCEDURE assign_task_id;
  393.   BEGIN
  394.     id := 1;
  395.     WHILE (id_index [id] <> 0) DO
  396.       Inc (id);
  397.     task_info [task_num].id := id;
  398.     id_index [id] := task_num;
  399.   END;
  400.  
  401.  
  402. BEGIN {create_task}
  403.   IF ntasks >= max_tasks THEN
  404.     result := too_many_tasks
  405.   ELSE
  406.     BEGIN
  407.     task_num := Succ (ntasks);
  408.     create_stack;
  409.     IF result = 0 THEN
  410.       BEGIN
  411.       init_stack;
  412.       assign_task_id;
  413.       Inc (ntasks);
  414.       END
  415.     END;
  416. END;
  417.  
  418.  
  419. {Switch to the next task}
  420.  
  421. PROCEDURE switch_task;
  422.  
  423. VAR
  424.   new_stack: Pointer;
  425.   old_bp   : Word;
  426.   new_bp   : Word;
  427.  
  428. BEGIN
  429.  
  430.   {Only switch if there are other tasks to switch to}
  431.  
  432.   IF ntasks > 1 THEN
  433.     BEGIN
  434.  
  435.     {Save the current value of SS, SP, and BP for this task}
  436.  
  437.     INLINE
  438.       (
  439.       $89/$ae/>old_bp           {MOV  OLD_BP,BP}
  440.       );
  441.     WITH task_info [current_task] DO
  442.       BEGIN
  443.       stack_ptr := Ptr (Sseg, Sptr);
  444.       bp        := old_bp;
  445.       END;
  446.  
  447.     {Switch to the next task.  The bit with new_stack and new_bp are because
  448.     it's easier to write INLINE code to access a simple variable than it is
  449.     to access a record of an array.}
  450.  
  451.     IF current_task >= ntasks THEN
  452.       current_task := 1
  453.     ELSE
  454.       Inc (current_task);
  455.     WITH task_info [current_task] DO
  456.       BEGIN
  457.       new_stack := stack_ptr;
  458.       new_bp    := bp;
  459.       END;
  460.     INLINE
  461.       (
  462.       $8b/$86/>new_stack+0/     {MOV  AX,[BP].NEW_STACK+0}
  463.       $8b/$96/>new_stack+2/     {MOV  DX,[BP].NEW_STACK+2}
  464.       $8b/$ae/>new_bp/          {MOV  BP,[BP].NEW_BP}
  465.       $Fa/                      {CLI}
  466.       $8e/$d2/                  {MOV  SS,DX}
  467.       $8b/$e0/                  {MOV  SP,AX}
  468.       $fb                       {STI}
  469.       );
  470.     END;
  471. END;
  472.  
  473.  
  474. {Return the id number of the currently executing task}
  475.  
  476. FUNCTION current_task_id: task_id;
  477. BEGIN
  478.   current_task_id := task_info [current_task].id;
  479. END;
  480.  
  481.  
  482. {Return the number of tasks}
  483.  
  484. FUNCTION number_of_tasks: task_number;
  485. BEGIN
  486.   number_of_tasks := ntasks;
  487. END;
  488.  
  489.  
  490. {Return a copy of the task info array, as well as the number of tasks.}
  491.  
  492. PROCEDURE get_task_info
  493.   (
  494.   VAR info: task_info_array;
  495.   VAR n   : task_number
  496.   );
  497. BEGIN
  498.   n    := ntasks;
  499.   info := task_info;
  500. END;
  501.  
  502.  
  503. {Initialize this unit.  The task list is initialized to contain the
  504. current task, whose task id is 1.}
  505.  
  506. PROCEDURE init_mtask;
  507. VAR
  508.   id: task_id;
  509. BEGIN
  510.   FOR id := 1 TO max_tasks DO
  511.     id_index [id] := 0;
  512.   ntasks := 1;
  513.   current_task := 1;
  514.   WITH task_info [current_task] DO
  515.     BEGIN
  516.     stack_org   := NIL;
  517.     stack_bytes := 0;
  518.     id          := 1;
  519.     id_index [id] := current_task;
  520.     END;
  521. END;
  522.  
  523.  
  524. BEGIN {mtask}
  525.   init_mtask;
  526. END.
  527.